using System;
using System.Data;
using System.Configuration;
using System.Text;
using System.Web;
using System.DirectoryServices;
using System.Collections.Generic;
using System.Collections;
using System.Xml;
using Egora.Pvp;

namespace Egora.Stammportal.LdapAuthorizationService
{
  public class PvpApplicationLdapAuthorizer
  {
    public PvpApplicationLdapAuthorizer(string rootUrl, string userId)
      : this (rootUrl, userId, LdapConfiguration.GetConfiguration ())
    {
    }

    public PvpApplicationLdapAuthorizer(string rootUrl, string userId, LdapConfiguration configuration)
    {
      if (rootUrl == null)
        throw new ArgumentNullException ("rootUrl");

      if (userId == null)
        throw new ArgumentNullException ("userId");

      if (configuration == null)
        throw new ArgumentNullException ("configuration");

      _application = configuration.GetApplication (rootUrl);
      if (_application != null)
      {
        if (!String.IsNullOrEmpty (_application.DomainPrefix) 
          && userId.StartsWith (_application.DomainPrefix + @"\", StringComparison.InvariantCultureIgnoreCase))
        {
          userId = userId.Remove (0, _application.DomainPrefix.Length + 1);
        }
      }
      _userId = userId;
      _rootUrl = rootUrl;
      _isValid = (_application != null) && (!_application.MustHaveRole || HasRole);
    }

    protected bool HasRole
    {
      get { return !String.IsNullOrEmpty(Roles); }
    }

    private bool _isValid = false;
    public bool IsValid
    {
      get { return _isValid; }
    }

    public bool IsWeb
    {
      get { return IsValid && _application.IsWeb (_rootUrl); }
    }

    public bool IsSoap
    {
      get { return IsValid && _application.IsSoap (_rootUrl); }
    }

    private string _userId;
    public string UserId
    {
      get { return _userId; }
      set { _userId = value; }
    }
    
    private string _rootUrl;
    private ApplicationConfiguration _application;
    private DirectoryEntry _user;
    public DirectoryEntry User
    {
      get
      {
        if (_user == null)
          _user = GetOneUser (_userId);

        return _user;
      }
    }

    private string _mail;
    public string Mail
    {
      get
      {
        if (_mail == null)
          _mail = GetAttributeValue (PvpIdentifier.X_AUTHENTICATE_mail);

        return _mail;
      }
    }

    private string _pvpUserId;
    public string PvpUserId
    {
      get
      {
        if (_pvpUserId == null)
          _pvpUserId = GetAttributeValue(PvpIdentifier.X_AUTHENTICATE_UserId);

        return _pvpUserId;
      }
    }

    public string ApplicationName
    {
      get { return _application == null ? null : _application.Name; }  
    }

    private string _commonName;
    public string CommonName
    {
      get
      {
        if (_commonName == null)
        {
          _commonName = GetAttributeValue (PvpIdentifier.X_AUTHENTICATE_cn);
        }

        return _commonName;
      }
    }

    public string GetAttributeValue(string attributeName)
    {
      if (User == null || !IsValid)
        return null;

      string[] values = _application.GetAttributeValue (attributeName, User, Groups);
      if (values == null)
        return null;
      return String.Join (",", values);
    }

    private XmlElement _pvpSoapFragment = null;
    public XmlElement PvpSoapFragment
    {
      get
      {
        if (User==null)
          return null;

        if (_pvpSoapFragment == null)
        {
          XmlDocument doc = new XmlDocument ();
          string principal = String.Format (_application.SoapPrincipalFragment,
                  EncodingUtil.XmlEncode(_userId),
                  EncodingUtil.XmlEncode(CommonName),
                  EncodingUtil.XmlEncode(GvOuID),
                  EncodingUtil.XmlEncode(Ou),
                  EncodingUtil.XmlEncode(Mail),
                  EncodingUtil.XmlEncode(Telephone),
                  EncodingUtil.XmlEncode(GvSecClass),
                  EncodingUtil.XmlEncode(GvGID),
                  EncodingUtil.XmlEncode(GvFunction));
          string pvpToken = String.Format (
            Properties.Settings.Default.PvpTokenFormat,
            EncodingUtil.XmlEncode(Version),
            EncodingUtil.XmlEncode(ParticipantID),
            principal,
            SoapRoles);

          doc.LoadXml (pvpToken);
          _pvpSoapFragment = doc.DocumentElement;
        }

        return _pvpSoapFragment;
      }
    }

    private string _soapRoles = null;
    public string SoapRoles
    {
      get
      {
        if (User == null)
          return null;

        if (_soapRoles == null)
        {
          _soapRoles = Authorization.GetSoapFragment ();
        }

        return _soapRoles;
      }
    }

    private string _roles;
    public string Roles
    {
      get
      {
        if (User == null)
          return null;

        if (_roles == null)
        {
          _roles = Authorization.ToString ();
        }

        return _roles;
      }
    }

    public string ParticipantID
    {
      get { return GetAttributeValue (PvpIdentifier.X_AUTHENTICATE_participantId); }
    }

    public string GvGID
    {
      get { return GetAttributeValue (PvpIdentifier.X_AUTHENTICATE_gvGid); }
    }

    public string GvOuID
    {
      get { return GetAttributeValue (PvpIdentifier.X_AUTHENTICATE_gvOuId); }
    }

    public string GvOuDomain
    {
      get { return GetAttributeValue (PvpIdentifier.X_AUTHENTICATE_gvOuDomain); }
    }

    public string Ou
    {
      get { return GetAttributeValue (PvpIdentifier.X_AUTHENTICATE_Ou); }
    }

    public string GvFunction
    {
      get { return GetAttributeValue (PvpIdentifier.X_AUTHENTICATE_gvFunction); }
    }

    public int AuthorizationTimeToLive
    {
      get { return _application.AuthorizationTimeToLive; }
    }

    public string Telephone
    {
      get { return GetAttributeValue (PvpIdentifier.X_AUTHENTICATE_tel); }
    }

    public string Version
    {
      get { return GetAttributeValue (PvpIdentifier.X_Version); }
    }

    public string GvSecClass
    {
      get { return GetAttributeValue (PvpIdentifier.X_AUTHENTICATE_gvSecClass); }
    }

    public string AuthorizeGvOuID
    {
      get { return GetAttributeValue (PvpIdentifier.X_AUTHORIZE_gvOuId); }
    }

    public string AuthorizeOU
    {
      get { return GetAttributeValue (PvpIdentifier.X_AUTHORIZE_Ou); }
    }

    public string CostCenterId
    {
      get { return GetAttributeValue(PvpIdentifier.X_ACCOUNTING_CostCenterId); }
    }

    public string ChargeCode
    {
      get { return GetAttributeValue(PvpIdentifier.X_ACCOUNTING_ChargeCode); }
    }

    public string InvoiceRecptId
    {
      get { return GetAttributeValue(PvpIdentifier.X_ACCOUNTING_InvoiceRecptId); }
    }

    private PvpAuthorization _authorization;
    private PvpAuthorization Authorization
    {
      get
      {
        if (_authorization == null && _application != null)
        {
          _authorization = new PvpAuthorization ();
          string[] roles =  _application.GetAttributeValue (PvpIdentifier.X_AUTHORIZE_roles, User, Groups);
          if (roles != null)
          {
            foreach (string role in roles)
            {
              if (!String.IsNullOrEmpty (role))
              {
                _authorization.Merge(role);
              }
            }
          }
        }
        return _authorization;
      }
    }

    private List<DirectoryEntry> _groups;
    private List<DirectoryEntry> Groups
    {
      get
      {
        if (_groups == null)
        {
          List<string> memberNames = new List<string> ();
          _groups = GetAllPvpApplicationGroups (User, null, memberNames);
        }
        return _groups;
      }
    }

    private static Hashtable s_groupCache = new Hashtable ();

    private List<DirectoryEntry> GetAllPvpApplicationGroups(DirectoryEntry user, DirectoryEntry group, List<string> memberNames)
    {
      DirectoryEntry member = user;
      if (member == null)
        member = group;
      List<DirectoryEntry> groups = new List<DirectoryEntry> ();
      string memberDistinguishedName = member.Properties["distinguishedName"].Value.ToString ();

      string key = memberDistinguishedName + _application.GroupContainer;
      if (user == null && Properties.Settings.Default.CacheGroupResolution)
      {
        if (s_groupCache.ContainsKey (key))
          return (List<DirectoryEntry>)s_groupCache[key];
      }

      if (memberNames.Contains (memberDistinguishedName))
        return groups; 
      memberNames.Add (memberDistinguishedName);

      foreach (SearchResult result in GetPvpApplicationGroups (memberDistinguishedName))
      {
        groups.Add (result.GetDirectoryEntry ());
      }

      if (_application.RecurseGroupMembership)
      {
        foreach (string groupName in member.Properties["memberOf"])
        {
          DirectoryEntry root = new DirectoryEntry (_application.LdapRoot);
          root.AuthenticationType = AuthenticationTypes.Secure;

          DirectorySearcher search = new DirectorySearcher (
              root,
              String.Format (Properties.Settings.Default.GroupFilter, groupName));
          SearchResultCollection coll = search.FindAll ();

          if (coll.Count > 1)
            throw new ApplicationException (String.Format ("More than one object {0} found.", groupName));

          if (coll.Count == 1)
          {
            groups.AddRange (GetAllPvpApplicationGroups (null, coll[0].GetDirectoryEntry (), memberNames));
          }
        }
      }

      if (user == null && Properties.Settings.Default.CacheGroupResolution)
      {
        s_groupCache[key] = groups;
      }

      return groups;
    }

    private SearchResultCollection GetPvpApplicationGroups(string member)
    {
      DirectoryEntry zmrGroupContainer = new DirectoryEntry (_application.GroupContainer);
      zmrGroupContainer.AuthenticationType = AuthenticationTypes.Secure;

      DirectorySearcher search = new DirectorySearcher (
          zmrGroupContainer,
          String.Format (Properties.Settings.Default.ApplicationGroupFilter, member));
      SearchResultCollection coll = search.FindAll ();

      return coll;
    }

    public SearchResultCollection GetUsers(string userId)
    {
      DirectoryEntry root = new DirectoryEntry (_application.LdapRoot);
      root.AuthenticationType = AuthenticationTypes.Secure;

      DirectorySearcher search = new DirectorySearcher (root, String.Format (Properties.Settings.Default.UserFilter, userId));
      SearchResultCollection coll = search.FindAll ();
      return coll;
    }

    private DirectoryEntry GetOneUser(string userId)
    {
      SearchResultCollection coll = GetUsers (userId);

      if (coll.Count > 1)
        throw new ApplicationException (String.Format ("More than one User {0} found.", userId));

      if (coll.Count < 1)
        return null;

      DirectoryEntry user = coll[0].GetDirectoryEntry ();
      if (_application.UserProperties != null)
        user.RefreshCache (_application.UserProperties.Split(','));
      return user;
    }

  }
}
